/**
 * Copyright 2010 Victor Egea Hernando
 *
 * RockScript is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, version 3 of the License.
 * 
 * RockScript is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public License
 * along with RockScript.  If not, see <http://www.gnu.org/licenses/>.
 */
#include <ctime>
#include <cstdlib>
#include <iostream>
#include <paper/paper.h>
#include <corelib/corelib.h>

// VM handler definition
class vm_test1_handler: public paper::vmachine_hnd {

public:
	// dynamic error report
	void dynError(const paper::vm_error &error) {
		std::cout << "error: " << error.error() << std::endl;
	}

	// 
	void open(const corelib::ustring &path) {

	}

	// 
	void compile(const corelib::ustring &path) {

	}

	// 
	corelib::uint8 getc( void )
	{

	}

	// 
	void close() {

	}

};

class test {

public:
	int main();
	int load_class_method();
	int load_code_miscellanea();

protected:
	// cpp handlers
	static corelib::int32 gcd(paper::vmachine &vm, paper::method &mp,
			corelib::uint32 pc);
	static corelib::int32 lcm(paper::vmachine &vm, paper::method &mp,
			corelib::uint32 pc);

protected:
	// VM & handler
	paper::vmachine vm;
	vm_test1_handler hnd;
	// classes and methods
	paper::vm_class* _class;
	paper::method* _method1;
	paper::method* _method2;

};

int test::main() {
	int ret = 0;

	// set handler
	vm.set_hnd(hnd);

	// load classes and methods
	if (ret = load_class_method() | load_code_miscellanea()) {
		return ret;
	}

	reginteger( vm.argument( 1 ) ) = 13833;
	reginteger( vm.argument( 2 ) ) = 17013;

	// call function
	if (!vm.run(_class->name(), _method2->meta_name())) {
		return -1;
	}

	std::cout << "lcm(13833,17013): " << reginteger( vm.lret() ) << std::endl;
	return 0;
}

int test::load_class_method() {
	/** 
	 * add class
	 */
	corelib::pair<paper::vm_class*, bool> testc = vm.add_class("test.Test");
	// check addition
	if (!testc.second) {
		return -1;
	}
	// get class
	_class = testc.first;

	/** 
	 * add methods
	 */
	corelib::pair<paper::method*, bool> testm1 = _class->add_method("gdc",
			"mthd_2_", 2);
	// check addition
	if (!testm1.second) {
		return -1;
	}
	// get class
	_method1 = testm1.first;

	corelib::pair<paper::method*, bool> testm2 = _class->add_method("lcm",
			"mthd_2_", 2);
	// check addition
	if (!testm2.second) {
		return -1;
	}
	// get class
	_method2 = testm2.first;

	//
	return 0;
}

int test::load_code_miscellanea() {
	/* 
	 * Binary GCD algorithm {u, v}
	 */

	//allocate memory
	if (!_method1->set_stack_frame_size(0, 1)) {
		std::cout << "error allocating gcd memory" << std::endl;
		return -1;
	}

	// set method static
	_method1->set_flags(paper::FLG_MTHD_STATIC);
	_method1->set_cpp_handler(&gcd);

	// execute cpp code and emit a return
	_method1->add_inst_b(paper::OP_CPPCODE, 0, 0);
	_method1->add_inst_b(paper::OP_RETURN, 11, 0);

	/* 
	 * Least common multiple
	 */

	// allocate memory
	if (!_method2->set_stack_frame_size(0, 4)) {
		std::cout << "error allocating lcm memory" << std::endl;
		return -1;
	}

	// set method static
	_method2->set_flags(paper::FLG_MTHD_STATIC);
	_method2->set_cpp_handler(&lcm);

	// R10 = method1(a,b)
	_method2->add_inst_a(paper::OP_LOADARG, 1, 1, 0);
	_method2->add_inst_a(paper::OP_LOADARG, 2, 2, 0);
	_method2->add_inst_b(paper::OP_KCCALL, _method1->idx(),_method1->pclass()->idx());
	// call cpp handler
	_method2->add_inst_b(paper::OP_CPPCODE, 0, 0);
	// return R10;
	_method2->add_inst_b(paper::OP_RETURN, 10, 0);

	return 0;
}

corelib::int32 test::gcd(paper::vmachine &vm, paper::method &mp, corelib::uint32 pc) {
	corelib::uint32 shift = 0;
	corelib::uint32 u = paper::reginteger(vm.spreg(REG_TOOGLE(1)));
	corelib::uint32 v = paper::reginteger(vm.spreg(REG_TOOGLE(2)));

	if (u == 0 || v == 0) {
		reginteger( vm.spreg( REG_TOOGLE(11) ) ) = u|v;
		return( 1 ); // pc++
	}

	for (; ((u | v) & 1) == 0; ++shift) {
		u >>= 1;
		v >>= 1;
	}

	while ((u & 1) == 0) u >>= 1;

	do {
		while ((v & 1) == 0)
			v >>= 1;
		if (u < v) {
			v -= u;
		} else {
			corelib::uint32 diff = u - v;
			u = v;
			v = diff;
		}
		v >>= 1;
	} while (v != 0);

	// set r11	
	reginteger( vm.spreg( REG_TOOGLE(11) ) ) = ( u << shift );
	return 1; // pc++
}

corelib::int32 test::lcm(paper::vmachine &vm, paper::method &mp,corelib::uint32 pc) {
	// return u * v / ret;
	corelib::uint32 u   = paper::reginteger(vm.spreg(REG_TOOGLE(1)));
	corelib::uint32 v   = paper::reginteger(vm.spreg(REG_TOOGLE(2)));
	corelib::uint32 ret = paper::reginteger(vm.spreg(REG_TOOGLE(10)));
	// set r10
	reginteger( vm.spreg( REG_TOOGLE(10) ) ) = ret ? u * v / ret : 0;
	return 1; // pc++
}

int main(char *argv[], int argc) {
	test test1;
	return test1.main();
}

